문단 클릭 = 시작 위치 선택 · 재생은 우하단 버튼/P (재생 중 클릭 = 점프)

5주차 — 이벤트 브로커 기반 초고확장 아키텍처와 바이브 코딩 에이전트

작은 EC2 스몰 인스턴스 하나로도 수천~수만 클라이언트를 감당하는 이벤트 브로커 아키텍처를 배운다. 트래픽 병목의 세 원인을 없애는 4단계 구성(pgmq·이벤트 브로커·팻 클라이언트·워커 서버)과, 이벤트 불변성이 만드는 락 없는 분산 처리를 이해하고, Docker 위 Express 하나로 이를 구현한 바이브 코딩 에이전트를 사례로 살핀다.


0. 사전 필수 용어 (선행지식)

본 강의는 Docker와 PostgreSQL을 어느 정도 안다는 전제로 전개된다. 아래 용어를 먼저 잡아두면 4단계 아키텍처 설명이 훨씬 잘 읽힌다.

1. 소켓(Socket) / 웹소켓(WebSocket) 소켓은 두 프로그램이 네트워크로 대화할 때 잡는 «전화선» 같은 통로다. HTTP는 요청 한 번에 응답 한 번을 받고 끊는 «단방향에 가까운» 통신이지만, 웹소켓은 한 번 연결하면 서버와 클라이언트가 서로 아무 때나 말을 걸 수 있는 «양방향» 통로다. 본문 3·4장의 «응답 대기 제거»와 «채널 기반 송수신»이 이 양방향 통로 위에서 성립한다.

2. 메시지 큐(Message Queue, MQ) 요청을 «대기줄»에 넣어두고 처리자가 하나씩 꺼내 가는 자료구조다. 은행 번호표 기계에 비유할 수 있다. 손님(요청)이 밀려도 번호표만 뽑아 두면 창구(처리자)가 자기 속도로 하나씩 부른다. 본문의 pgmq가 바로 이 번호표 기계 역할이다. 2장의 병목 해소와 5장의 신뢰 보정에서 핵심으로 쓰인다.

3. 트랜잭션(Transaction)과 원자성(Atomicity) 여러 작업을 «전부 성공 또는 전부 실패»로 묶는 단위다. 계좌 이체에서 출금과 입금이 함께 일어나거나 함께 취소되어야 하듯, 중간에 반쪽만 반영되면 안 된다. 6장에서 pgmq가 «큐에서 꺼내기»와 «테이블에 쓰기»를 하나의 트랜잭션으로 묶을 수 있다는 것이 가장 큰 장점으로 등장한다.

4. 역등성(Idempotency, 멱등성) 같은 요청을 여러 번 보내도 결과가 한 번 보낸 것과 같도록 보장하는 성질이다. 엘리베이터 버튼을 열 번 눌러도 한 번 부른 것과 같은 것과 비슷하다. 메시지가 중복 전달되거나 재처리돼도 시스템이 망가지지 않게 해주며, 6장의 «락 없는 읽기»가 안전한 근거가 된다.

5. 스케일 아웃(Scale Out) / 스케일 업(Scale Up) 스케일 아웃은 같은 서버를 «여러 대» 늘려 부하를 나누는 것(은행 지점을 더 만드는 것), 스케일 업은 서버 한 대의 «성능»을 키우는 것(50평 지점을 200평으로 넓히는 것)이다. 본 아키텍처의 목표는 각 구성 요소를 끝없이 스케일 아웃할 수 있게 만드는 데 있다.

📚 참고 — 이벤트 소싱(Event Sourcing)과 CQRS, 트랜잭셔널 아웃박스(Transactional Outbox) 같은 패턴을 더 깊이 보고 싶다면 별도의 선행 학습 자료(예: 「AI 엔지니어링 EP0 - 선수지식」 또는 PostgreSQL·메시지 큐 공식 문서)를 참고하라. 본 강의는 이 개념들의 «실전 조립»에 초점을 둔다.


1. 주제 정의

이 강의의 주제는 «작은 인스턴스 하나로 초대형 트래픽을 버티는 이벤트 브로커 아키텍처» 이며, 그 실증으로 바이브 코딩 에이전트(Vibe Coding Agent) 를 이 아키텍처 위에 얹어 만든 과정을 보여준다.

강의는 첫머리에서 «작은 EC2 스몰만 가지고도 엄청난 트래픽을 버틸 수 있다 — 거짓말 같지만 진짜»라고 단언한다. 이 주장이 성립하는 이유를 이해하는 것이 강의 전체의 목표다. 핵심은 트래픽을 유발하는 병목을 구조적으로 제거하고, 모든 통신을 «이벤트»라는 불변의 메시지로 환원하는 데 있다.

또한 이 아키텍처는 특정 개인의 발명이 아니라 «익히 알려져 있는, 초대형 규모 트래픽을 받는 아키텍처»임을 분명히 한다. 즉 이벤트 브로킹 시스템은 대형 서비스를 만들 때 가장 합리적인 선택 중 하나이며, 스케일 아웃이 «끝없이» 가능하다는 점이 그 근거다.

이 개념은 여러 곳에 재사용된다. 강의에서는 «거의 모든 서비스에 적용 가능한 굉장히 안정적인 아키텍처»라고 표현한다. 특정 서비스를 위한 일회성 설계가 아니라, 한 번 만들어 두면 애플리케이션만 바꿔 끼우는 «인프라 골격»으로 쓰는 것이 핵심 의도다.


2. 풀려는 문제 — 트래픽 병목의 세 가지 원인

트래픽이 몰리면 왜 시스템이 느려지는가. 강의는 은행 창구 비유로 병목을 세 종류로 나눈다.

① 접수 대기 병목 동시에 여러 요청이 밀려오면 «줄 서서 들어가는 것»만으로도 하세월이 된다. 은행 창구에 사람이 몰린 경우, 또는 피크 시간에 게임(디아블로) 서버 접속 대기열에 걸리는 경우가 여기에 해당한다. 요청을 «접수»하는 입구 자체가 막히는 문제다.

② 요청 처리 병목 행원에게 대출 심사처럼 «오래 걸리는 힘든 일»을 시키면 그 창구는 한동안 블로킹 상태가 된다. 뒤의 손님들이 줄줄이 밀린다. 처리 로직이 무겁거나 오래 걸려 다른 요청이 대기하는 문제다.

③ 응답 대기 병목 가장 미묘한 병목이다. 클라이언트가 응답을 기다리며 «자리를 안 비키고» 창구를 차지하고 있으면, 한정된 공간(은행)에 새 손님이 못 들어온다. 기술적으로는 서버가 소켓을 풀지 못하고 있는 상태다. 서버가 요청을 빨리 끝내고 클라이언트와의 소켓 연결을 풀어야 새 소켓을 받는데, 클라이언트가 응답을 받을 때까지 소켓을 물고 있으면 그 소켓이 점점 쌓여 더 이상 받을 수 없게 된다.

⚠️ 주의 — 세 병목 중 응답 대기가 특히 위험한 이유는, 처리 능력이 남아 있어도 «소켓 자원 고갈»만으로 서버가 신규 연결을 거부하기 때문이다. 리눅스 기준 대략 3천 개의 소켓을 넘어가면 TCP가 꽉 차 효율이 떨어진다고 알려져 있다(강의 언급 수치).

스케일로 극복하는 세 가지 방식

병목을 흡수하는 전통적 방법도 세 가지로 정리된다.

  • 스케일 아웃: 은행 «지점을 많이» 만든다. 가장 무식하지만 확실한 방법.
  • 스케일 업: 은행 하나를 «50평에서 200평»으로 키운다.
  • 분산 접수: 요즘 은행 앱처럼 «방문 예약»을 받아 여러 창구로 접수를 나눈다. 처리량 자체는 같지만 입구의 혼잡을 분산한다.

이 강의의 아키텍처는 세 방식을 «구성 요소별로 독립적으로» 적용할 수 있게 만든다. 접수(이벤트 브로커)와 처리(워커 서버)를 분리해 두면 각각 필요한 만큼만 스케일 아웃하면 된다.


3. 핵심 개념·구조

3-1. 병목 제거 + 신뢰성 확보 6대 전략

목표는 두 가지다 — 신뢰성 확보(요청·응답이 유실되지 않을 것)와 병목 제거. 은행에서 «137번 손님 계세요?»가 골치인 것처럼, 요청을 받아 놓고 잃어버리면 안 된다. 이를 위한 기술적 접근이 여섯 가지다.

  1. 고속 MQ 서버 이용 — 요청·응답 메시지를 함부로 지우지 않고, 확인이 끝난 것만 지울 수 있는 구조로 신뢰성을 높인다.
  2. 역등성 구현 — 여러 개의 메시지를 보내도 그 «키»가 같다면 하나의 메시지로 인식한다.
  3. 응답 대기 제거(CPS) — 응답을 기다리게 하지 않는다. 고객센터가 «지금 밀리니 여유 날 때 다시 전화할게, 끊어»라고 하는 것과 같다. «요청만 하고 꺼져라, 내가 다시 보내줄게» — 이 방식을 강의는 지난 시간에 CPS라고 불렀다.
  4. 양방향 통신 — CPS가 되려면 상대방도 응답을 받을 창구를 열어둬야 한다. HTTP는 요청을 보내면 반드시 응답을 대기하지만, 여기서는 요청 보내고 소켓을 완전히 끊었다가, 서버가 응답을 만들었을 때 클라이언트에게 «쏘는» 양방향 통신(웹소켓)이 필요하다.
  5. 채널 기반 송수신 — 서버가 특정 클라이언트를 «직접 알고 1:1로 관리»하지 않는다. 클라이언트는 «나는 A·B 채널을 수신», 서버는 «C·D 채널을 수신»처럼 채널 구독으로만 주고받는다. 그러면 세션 «식별»이 사라지고 채널 전송만 남아 관리 부담이 준다.
  6. 워커 스레드 풀 + 비동기화 — 작업을 «짧게 짧게» 비동기로 처리해 이벤트 큐에 계속 쌓는 방식으로 부하를 관리한다.

3-2. 4단계 아키텍처 구성 요소

강의가 제시하는 확장성 높은 시스템은 네 개의 축으로 이루어진다. OCR로 확인된 슬라이드 «참가자 구성»에도 동일하게 정리되어 있다.

# 구성 요소 한 줄 역할 스케일 방식
1 pgmq (PostgreSQL Message Queue) 이벤트를 신뢰성 있게 저장하는 메시지 큐 이벤트 타입별로 여러 대
2 이벤트 브로커 클라이언트가 접속하는 소켓 서버, 중개만 담당 접수 창구를 여러 대
3 클라이언트(팻 클라이언트) 이벤트를 요청·수신하고 스스로 처리 사용자 수만큼
4 워커 서버 이벤트를 가져가 실제 비즈니스 로직 수행 이벤트 종류별로 여러 대

💡 실무 노하우 — pgmq 입장에서 보면 이벤트 브로커와 워커 서버가 둘 다 «클라이언트» 다. pgmq는 누가 접속했는지, 메시지 내용이 무엇인지 «관심이 없다». 이 무관심이 재사용성의 뿌리다(6장 참조).

각 구성 요소의 상세는 4장(구현 가이드)에서 단계별로 다룬다.


4. 실습·구현 가이드 (Step 1·2·3·4)

네 구성 요소를 만드는 순서로 정리한다. pgmq → 이벤트 브로커 → 클라이언트 → 워커 서버.

Step 1 — pgmq(메시지 큐)로 신뢰성 기반 마련

pgmq는 PostgreSQL 기반 메시지 큐 확장이다. 저장소로 (메모리) 테이블을 그대로 쓴다. 강의가 강조하는 두 가지 특성이 있다.

(a) 읽기 락을 걸지 않는다. PostgreSQL 테이블에는 락을 풀어 두는 기능이 있어, 읽는 동안 read lock을 걸지 않을 수 있다. 쓰기 락(write lock)은 어쩔 수 없지만, 읽기 락이 없으면 어떤 워커는 레코드 10개를 읽고 다른 워커는 그 사이 추가된 11개를 읽어 «불일치»가 생길 수 있다. 그래도 상관하지 않는다 — 역등성 덕분에 나중에 다시 읽어 중복이 생겨도 큰 문제가 안 되기 때문이다. 그래서 read lock을 안 걸어 «엄청 빠르게» 작동한다.

(b) MQ와 영속화 테이블을 하나의 트랜잭션으로 묶는다. pgmq는 모든 것이 하나의 PostgreSQL 안에 들어 있으므로, «큐에서 꺼내기»와 «테이블에 쓰기»를 원자적으로 묶을 수 있다. 원격 MQ 서버(예: AWS SQS, MQTT)로는 불가능한 일이다. 그런 시스템에서는 영속화 테이블 사이 데이터 파이프라이닝에서 유실이 나 이를 보정하려 «난리»를 쳐야 한다.

pgmq가 지원하는 연산은 다섯 가지다 — write, read, delete, set_vt, archive. 이 중 set_vtarchive는 신뢰 보정과 관련되며 6장에서 자세히 다룬다.

✅ 확인 — 큐에 메시지를 넣고 꺼내는 과정에서 유실이 없고, «꺼내기»와 «지우기»가 분리되어 그 사이에 워커가 죽어도 메시지가 살아남는지 확인하라. 이 «분리»가 신뢰성의 핵심이다.

Step 2 — 이벤트 브로커(소켓 서버) 구현

이벤트 브로커는 클라이언트가 직접 접속하는 소켓 서버다. 클라이언트가 보기에는 이것이 서버로 보인다. 브로커가 하는 일은 세 가지뿐이다.

  1. 클라이언트가 던진 이벤트를 pgmq에 삽입(write) 한다. 클라이언트는 pgmq에 직접 쓰지 않는다.
  2. 클라이언트가 구독한 채널용 이벤트만 pgmq에서 모아서 송신한다.
  3. pgmq의 이벤트를 인메모리로 옮겨 캐시한 뒤 자기 담당 클라이언트에게 쏜다.

핵심은 브로커가 메시지 «처리»를 전혀 하지 않는다는 점이다. «관심이 없고» 오직 브로킹만 담당한다. 그래서 브로커를 여러 개 붙이면 «접수 창구»를 여러 개 만들 수 있다. 브로커 1번이 클라이언트 1~100번, 브로커 2번이 101~200번을 담당하는 식이다.

이 구조의 이점은 pgmq 부하 감소다. pgmq는 «브로커 몇 개»만 상대하면 되므로, 클라이언트가 직접 달려드는 것보다 훨씬 낫다. 마치 MySQL 프록시 서버처럼 브로커가 DB 부하를 대신 받아준다. 브로커 100개만 있어도 수만 개의 클라이언트를 처리할 수 있고, 사용자가 늘면 EC2 스몰로 브로커 하나만 더 띄우면 된다.

💡 실무 노하우 — pgmq 자체도 스케일 아웃할 수 있다. 마스터-슬레이브가 아니라 완전히 별개의 pgmq 서버를 여러 대 띄우고, 메시지 안에 기술된 «이벤트 타입»에 따라 어느 pgmq에 쓸지 결정하면 된다. 브로커가 여러 pgmq를 바라보며 정기적으로 가져와 인메모리에 캐시한다.

Step 3 — 클라이언트(팻 클라이언트) 개발

클라이언트는 이벤트 브로커에 소켓으로 연결되어, 원하는 이벤트 카테고리(채널)만 수신하고, 필요한 요청은 이벤트로 만들어 브로커에 쏜다. 브로커는 «페이로드가 담긴 이벤트»만 전달하므로, 그 이벤트로 무엇을 할지는 전부 클라이언트에 구현돼 있어야 한다.

그래서 이 아키텍처의 클라이언트는 필연적으로 팻 클라이언트(fat client) 가 된다. 강의는 «신 클라이언트가 아니라 상당한 팻 클라이언트»라고 말한다. 요즘 유행하는 코덱스·클로드코드 같은 도구들이 바로 어마어마한 팻 클라이언트(러스트로 짠 큰 코드베이스)의 예다.

📚 참고 — 팻 클라이언트는 웹에서 배포가 어렵고 앱 유지보수비가 크다는 단점 때문에 한동안 신 클라이언트(웹사이트)가 주류였다. 그러나 «유행은 돌고 돌아» 다시 팻 클라이언트 시대로 오고 있고, 이벤트 브로킹 시스템은 팻 클라이언트와 «장난 아니게» 잘 어울린다. HTML 같은 무거운 렌더링 로직을 줄 필요 없이 이벤트 페이로드만 던지면 되기 때문이다.

Step 4 — 워커 서버 구현

워커 서버가 «핵심»이다. 이벤트 브로커가 송수신만 하는 데 비해, 워커 서버는 또 다른 pgmq를 바라보며 실제 처리를 한다. 동작 흐름은 이렇다.

  1. pgmq에 접속해 «자기가 처리해야 하는 이벤트»가 있는지 읽어본다.
  2. 있으면 가져가서 처리한다.
  3. 처리가 끝나면 대기하지 않고 결과가 담긴 새 이벤트를 다시 pgmq에 등록(write) 한다.
  4. 그러면서 가져갔던 원본 작업을 삭제(delete) 한다.

등록된 결과 이벤트는 이벤트 브로커가 들고 가 해당 채널을 구독한 클라이언트에게 쏜다. 이 구조 때문에 모든 «대기»가 사라진다.

워커 서버의 또 다른 큰 강점은 관심사별 분리다. 1번 워커는 이벤트 타입 1~5번, 2번 워커는 타입 A~Z를 처리하는 식으로 자기 관심사만 담당한다. 특정 타입의 부하가 높으면 그 타입만 처리하는 워커를 추가하면 된다. 머신도 인스턴스별로 갈릴 수 있다.

⚠️ 주의 — 워커 서버는 클라이언트나 이벤트 브로커에 대한 직접적 지식이 전혀 없다. «이벤트가 있나? 처리하고 다시 넣어둔다»가 전부다. 모든 작업이 클라이언트·브로커의 요청과 완전히 격리되고 이벤트만 기반으로 움직인다. 이 격리가 워커 구현을 극도로 쉽게 만든다.


5. 사례·적용 — 바이브 코딩 에이전트

강의 후반부는 이 아키텍처로 만든 바이브 코딩 에이전트를 시연·해설한다. 아직 «개념 증명(PoC)» 수준이라 UI는 신경 쓰지 말라고 전제한다.

5-1. Docker 위에서 돈다

에이전트는 웹사이트로 구동되며 Docker 컨테이너 위에서 실행된다. 따라서 에이전트가 만드는 프로젝트 파일은 로컬 PC가 아니라 Docker 안의 볼륨에 써진다. 볼륨이 곧 «워크스페이스(프로젝트 루트 폴더)»이고, 그 밑에 세션별 폴더가 생겨 각 세션 하나가 하나의 프로젝트가 된다.

5-2. Express 하나가 브로커·백엔드·웹소켓을 겸한다

라우터는 쓰지 않는다. 워커는 따로 돌고, 에이전트 서버 안에 이벤트 브로커가 함께 돈다. 로그인·로그아웃 같은 클라이언트 접대는 웹 서버가 처리한다. 하나의 Express 서버가 소켓 서버(이벤트 브로커) 역할과 프론트엔드 백엔드 역할을 동시에 하기 때문에 포트가 하나만 필요하다.

구조는 apps + packages로 나뉜다. packages에는 agent 라이브러리가 있는데, - 브로커는 거의 완성되어 «가져다 쓰기만» 하면 되고, - 워커는 구현체가 아니라 «워크스레드 풀을 어떻게 운영할지»에 대한 기본 인터페이스만 제공한다. 실제 워커는 각 애플리케이션이 구현한다.

agent는 완전히 독립적인 순수 라이브러리(외부 의존성만 존재)라 다른 프로젝트에도 쓸 수 있다. common에는 프로토콜·이벤트 타입·세션이 정의돼 있다.

5-3. 도구는 bash 하나뿐 — 나머지는 스킬로

이번 에이전트는 도구로 bash 하나만 준다. bash는 Node.js의 spawn으로 프로세스를 띄운다(Docker는 리눅스 환경이라 bash가 항상 있다). Promise로 감싸 비동기·타이머·표준 입출력 통신을 처리한다.

강의는 도구 구현에 두 방향이 있다고 정리한다 — 코덱스는 shell로 다 처리하고, Anthropic 계열은 도구를 아주 많이 준다. 이번에는 «bash만 쓰는 애»를 만들고, 파일 찾기·쓰기 등 나머지 기능은 스킬(skill) 로 떼어 제공할 계획이다(«파일 찾을 때는 이 스킬을 써라»). 훅(hook)·스킬은 아직 미구현이며 다음 시간에 만든다.

5-4. 스트리밍은 «이벤트로 말아서 쏜다»

기본 bash 도구는 표준 출력을 받아 stdout에 넣기만 하고 스트리밍이 없다. 스트리밍을 만들려면, 중간중간 출력이 생길 때마다 이벤트를 생성해 pgmq에 넣고, 이벤트 브로커가 그것을 들고 가 클라이언트에게 «바시가 실행 중입니다» 식으로 중계하게 해야 한다. 이 기능은 «공짜가 아니라» 직접 만들어야 한다.

그러나 일단 만들고 나면 통신을 아예 잊어버릴 수 있다. 클라이언트에 뭔가 쏘고 싶으면 그저 «이벤트로 말아서 여기 넣는 것»이 전부다. LM이 스트림을 쏘든, 도구가 stdout을 뱉든, 워커는 «아무 생각 없이» 전부 이벤트로 pgmq에 쏜다. 그래서 워커 구현이 «되게 쉬워진다».

💡 실무 노하우 — 시연에서는 LM Studio에 Nemotron 3.5 Lightning 모델을 잡아 계산기를 «턴 하나»로 만들어냈다. 어드민 사이트가 각 세션이 무엇을 했는지 감시하고, admin 계정은 Docker 환경 변수(admin-docker-env)로 지정한다.


6. 핵심 원리

6-1. 이벤트 불변성이 락을 없앤다

이벤트 브로커나 워커가 큐 읽기(read)를 «경쟁»하면 다시 대기가 걸려 똑같은 병목이 생긴다. 그런데 이 시스템에서는 이벤트가 불변(immutable) 값이다. 따라서 각 컴포넌트는 «나는 102번까지 읽었다»는 자기 기록만 유지하면 그 이후부터 쭉 읽어 가면 된다. 락도, 동기화도 필요 없다.

이것이 이벤트 소싱의 핵심이다 — 이벤트는 «이미 일어난 일»이므로 변하지 않는다. 덕분에 경합을 피하려 중간에 두던 라우터가 필요 없어지고, 팬아웃(fan-out) 방식으로 각자 가져가면 된다.

6-2. 트랜잭션 원자성이 신뢰성을 만든다

pgmq는 MQ와 테이블이 하나의 PostgreSQL 안에 있어 «큐에서 꺼내는 순간 테이블에 쓰기»를 원자화할 수 있다. 원격 MQ에서 흔한 «파이프라인 유실»이 원천적으로 없다. 강의는 이 불일치가 «얼마나 많은 문제를 일으키는지» 강조하며, 다른 시스템에서는 이를 보정하려 애쓰는 회사(스노우플레이크)가 «그걸로 돈을 번다»고까지 말한다.

6-3. set_vt 기반 MQ 신뢰 보정

워커가 메시지를 꺼낼 때의 흐름이 신뢰성의 정수다.

  1. 워커가 메시지를 꺼내면 set_vtVisible Timeout을 시작한다. 이는 일종의 «마커»로, 다른 워커가 이 메시지를 못 건드린다. 이 마킹은 원자성 연산이다.
  2. 워커가 타임아웃이 끝나기 전에 작업을 끝내면 delete로 지운다.
  3. 워커가 처리 도중 «죽어도» 상관없다. 타임아웃이 지나면 메시지가 자동으로 read 가능한 상태로 돌아와 다른 워커가 재처리한다.

이 «꺼내기와 지우기의 분리 + 타임아웃 복귀»가 MQ 신뢰 보정이며, 이 정도만 제공해도 충분한 신뢰성을 보장한다. Redis로도 가능하지만 메서드가 직접 제공되지 않아 키를 직접 써야 한다(Redis는 원자적이므로 가능).

6-4. 영원한 재사용성

애플리케이션을 만든다는 것은 결국 클라이언트와 워커가 «이벤트 타입 협약(이벤트 프로토콜)»을 맺는 것뿐이다. 이벤트 브로커와 pgmq는 «어떤 이벤트인지조차 관심이 없다» — 달라니까 줬고, 넣어달라니까 넣었을 뿐이다.

그래서 브로커와 pgmq는 영원히 개발할 필요가 없다. 한 판만 만들어 두면, 새 애플리케이션은 클라이언트와 워커만 인터페이스에 맞춰 만들고 이벤트 타입만 맞춰주면 된다. 더 무서운 점은 애플리케이션 종류가 늘어도 같은 브로커·pgmq 인프라를 그대로 재사용할 수 있다는 것이다. 워커만 달라진다. 강의는 «사내의 모든 솔루션을 이 서버군에 다 밀어 넣을 수도 있다»고 말한다.


7. 한계·트레이드오프

이 아키텍처가 만능은 아니다. 강의에서 드러나는 대가와 제약을 정리한다.

  • 팻 클라이언트를 만들어야 한다. 많은 엔터프라이즈가 이 «가장 합리적인» 아키텍처를 못 쓰는 이유가 바로 이것이다. 팻 클라이언트는 웹 배포가 어렵고 앱 유지보수비가 크다. (다만 유행이 팻 클라이언트로 돌아오며 완화되는 중이다.)
  • 스트리밍 등 통신 기능을 직접 만들어야 한다. bash 도구의 스트리밍처럼, 편의 기능은 «공짜가 아니다». 출력마다 이벤트를 말아 pgmq에 넣는 코드를 직접 작성해야 한다.
  • 읽기 락을 포기한 대가로 «불일치»를 감수한다. 이는 역등성으로 방어하지만, 역등성을 보장하지 못하는 도메인이라면 이 최적화를 그대로 쓸 수 없다.
  • set_vt 타임아웃 값 조율이 필요하다. 너무 짧으면 정상 작업이 재처리되고, 너무 길면 죽은 워커의 메시지가 오래 방치된다. (일반 원칙 — 워커 평균 처리 시간을 기준으로 잡는다.)

⚠️ 주의 — PoC 단계라 스킬·훅·완전한 에이전트 기능이 아직 없다. «개념 구현은 했다» 수준이며, 실제 운영 수준으로 끌어올리려면 상당한 추가 구현이 필요하다.

로컬 LLM 운영의 하드웨어 한계 (후반부)

강의 후반부는 이 에이전트를 로컬 LLM으로 돌릴 때의 현실적 제약을 다룬다.

  • 양자화 하한선 — 강의는 «Q4는 다 갖다 버려», 코딩·로그 분석을 하려면 Q5~Q6 이상이 필요하다고 반복한다. 4비트짜리는 «안녕하세요»에나 답한다는 것.
  • KV 캐시 양자화의 함정 — KV 캐시를 Q8로 하면 VRAM이 절약되지만, 100회 이상 긴 세션에서 품질에 영향을 준다. 바이브 코딩을 제대로 하려면 이를 풀어야 하고, 그러면 VRAM을 «어마무지하게» 먹는다.
  • 애매한 용량 함정 — 128GB는 «애매한 용량»이다. 올릴 수 있는 모델이 별로 없고, KV 캐시 때문에 순식간에 점프한다. 강의는 오히려 64GB로 충분하다고 본다.

8. 다른 접근과 비교

8-1. 메시지 큐 대안 — pgmq vs Kafka/SQS/Redis

이 아키텍처는 pgmq에 의존적이지 않다. MQ 서버는 «가볍기만 하면» 되므로 Redis, Cassandra, 심지어 메모리 테이블을 쓰는 MySQL로도 가능하다. 좀 더 «빡센» 규모라면 Kafka도 쓸 수 있다.

대안 특징 이 아키텍처 관점
pgmq (PostgreSQL 확장) MQ+테이블 트랜잭션 원자화, read lock 미사용 유실 보정이 필요 없어 최강점
AWS SQS / MQTT 원격 관리형 MQ 영속화 테이블 사이 유실 보정 «난리»
Redis 원자적, 매우 빠름 신뢰 보정 메서드를 직접 구현해야
Apache Kafka 고처리량 분산 로그 대규모에 적합하나 무겁다

📚 참고 — 중요한 것은 «MQ 서버가 가볍고, 신뢰성을 위한 기능(set_vt 같은 가시성 제어, 트랜잭션)을 제공하는가»이다. pgmq의 결정적 우위는 PostgreSQL 안에서 MQ와 DB 트랜잭션을 «원자적으로 묶는» 능력이다. (참고 — pgmq는 오픈소스 PostgreSQL 메시지 큐 확장이다.)

8-2. 신 클라이언트 vs 팻 클라이언트

전통적 웹은 서버가 HTML까지 렌더링해 «신 클라이언트»로 내려줬다. 이 아키텍처는 이벤트 페이로드만 던지고 처리는 클라이언트가 하는 «팻 클라이언트»를 전제한다. 유지보수비 트레이드오프가 있지만, 코덱스·클로드코드처럼 강력한 데스크톱 도구 시대에는 팻 클라이언트가 다시 합리적 선택이 된다.

8-3. 로컬 LLM 모델·양자화 비교 (강의 소개 기준)

아래 모델·수치는 강의에서 소개한 내용을 그대로 옮긴 것이며, 특정 시점의 시장 상황을 반영한 언급이다. 최신 사양은 각 모델 공식 문서로 별도 확인하라.

  • MTP(Multi-Token Prediction) — 스페큘러티브 디코딩용 «별도 weight»다. 본체 모델 뒤에 붙어 draft 역할을 하며, 붙은 모델은 0.2~1GB 커진다. 설정은 MAX DRAFT TOKEN·MIN DRAFT TOKEN·DRAFT PROBABILITY. 강의 권장은 MAX 2, MIN 0. 원리상 한 번에 여러 토큰을 «추천»하는데 첫 토큰이 거부되면 뒤 추천을 모두 버리므로, 3~4개보다 2개 추천이 낫다. 속도 향상은 «2배»가 아니라 10~20% 수준.
  • QAT vs PTQ — PTQ는 «멀쩡한 모델을 나중에 양자화»한 것, QAT는 «양자화를 예상해 처음부터 학습»한 것이다. QAT는 4비트·8비트로 고정되며(3·2비트 없음), 같은 4비트라도 PTQ보다 성능이 높다(6~8비트에 가까운 성능, 모델마다 다름).
  • 멀티모달은 별도 모듈 — 이미지 타워·오디오 타워가 분리돼 있어(예: Gemma 계열 31B 중 오디오 인코더 300M·비디오 인코더 150M), 튜너가 이를 떼면 텍스트 전용 모델이 된다. 그래서 «원본이 멀티모달이어도 튜닝본은 아닐 수» 있으니 잘 골라야 한다. 스크린샷을 던져야 하는 바이브 코딩에서는 멀티모달 여부가 결정적이다.
  • 하드웨어 — DGX Spark(GB10, 128GB 통합 DDR5)는 «DDR5라 느리다»(20~40토큰 미만). 강의는 오히려 «ASUS TUF Gaming A16(약 230만 원)에 램을 올려 64GB + GPU 8GB = 72GB» 구성을 «선녀»라 부른다. 내장 GPU가 화면을 처리하므로 외장 GPU를 100% AI에 쓸 수 있기 때문이다. 기업 제안 시에는 보통 122B급(Q5 ≈ 90GB) 모델을 권한다.

9. 메타인지 자기평가

아래 질문에 «막힘없이» 답할 수 있으면 이 강의를 이해한 것이다.

개념 확인 - 트래픽 병목의 세 원인을 은행 비유 없이 기술 용어로 설명할 수 있는가? (특히 «응답 대기 = 소켓 미해제») - 4단계 구성 요소 각각의 «단 하나의 책임»을 한 문장으로 말할 수 있는가? - pgmq의 5연산(write·read·delete·set_vt·archive)을 나열하고 set_vt의 역할을 설명할 수 있는가?

원리 적용 - «이벤트가 불변이면 왜 락이 필요 없는가»를 스스로 유도할 수 있는가? - 새 애플리케이션을 추가할 때 «무엇만 만들면 되고, 무엇은 재사용되는가»를 구분할 수 있는가? - bash 도구에 스트리밍을 붙이려면 «어디에 이벤트를 넣어야» 하는지 그림을 그릴 수 있는가?

비판적 평가 - 이 아키텍처를 쓰기 «어려운» 상황(팻 클라이언트 부담, 역등성 불가 도메인)을 스스로 떠올릴 수 있는가? - pgmq 대신 Redis/Kafka/SQS를 골라야 하는 상황을 각각 하나씩 댈 수 있는가? - 로컬 LLM을 고를 때 Q5 하한선·멀티모달 여부·KV 캐시 트레이드오프를 종합해 «내 하드웨어에 맞는» 선택을 설명할 수 있는가?

✅ 확인 — 만약 «워커가 죽으면 메시지가 어떻게 되는가?»에 즉답할 수 없다면 6-3장(set_vt)을, «브로커가 pgmq 부하를 왜 줄이는가?»에 막힌다면 4장 Step 2를 다시 보라.

난이도
에피소드
질문
카드를 로딩 중...
답변

클릭하거나 Space를 눌러 뒤집기

0 / 0
학습 진도 0%
이동   Space 뒤집기   R 셔플   P 음성 버전 2026-08-05b